package com.jingdianjichi.subject.domain.service.impl;

import com.alibaba.fastjson.JSON;
import com.jingdianjichi.subject.common.entity.PageResult;
import com.jingdianjichi.subject.common.enums.IsDeletedFlagEnum;
import com.jingdianjichi.subject.common.utils.IdWorkerUtil;
import com.jingdianjichi.subject.common.utils.LoginUtil;
import com.jingdianjichi.subject.domain.convert.SubjectInfoConverter;
import com.jingdianjichi.subject.domain.entity.SubjectInfoBO;
import com.jingdianjichi.subject.domain.entity.SubjectOptionBO;
import com.jingdianjichi.subject.domain.handler.subject.SubjectTypeHandler;
import com.jingdianjichi.subject.domain.handler.subject.factory.SubjectTypeHandlerFactory;
import com.jingdianjichi.subject.domain.redis.RedisUtil;
import com.jingdianjichi.subject.domain.service.SubjectInfoDomainService;
import com.jingdianjichi.subject.domain.service.SubjectLikedDomainService;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.entity.SubjectInfoES;
import com.jingdianjichi.subject.infra.basic.ES.自定义ES_鸡翅.service.SubjectESService;
import com.jingdianjichi.subject.infra.basic.entity.SubjectInfo;
import com.jingdianjichi.subject.infra.basic.entity.SubjectLabel;
import com.jingdianjichi.subject.infra.basic.entity.SubjectMapping;
import com.jingdianjichi.subject.infra.basic.service.SubjectInfoService;
import com.jingdianjichi.subject.infra.basic.service.SubjectLabelService;
import com.jingdianjichi.subject.infra.basic.service.SubjectMappingService;
import com.jingdianjichi.subject.infra.rpc.UserRPC;
import com.jingdianjichi.subject.infra.rpc.entity.UserInfo;
import javafx.print.Collation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SubjectInfoDomainServiceImpl implements SubjectInfoDomainService {

    //主表的
    @Resource
    private SubjectInfoService infoService;

    //注入工厂
    @Resource
    private SubjectTypeHandlerFactory handlerFactory;


    @Resource
    private SubjectMappingService mappingService;

    @Resource
    private SubjectLabelService labelService;

    //ES交互Service
    @Resource
    private SubjectESService subjectESService;

    @Resource
    private UserRPC userRPC;

    @Resource
    private RedisUtil redisUtil;

    private static final String RANK_KEY="subject_rank";

    @Resource
    private SubjectLikedDomainService likedDomainService;

    //新增题目
    /*
        使用工厂+策略模式实现题目分别处理
     */
    @Override
    @Transactional
    public void add(SubjectInfoBO subjectInfoBO) {
        if (log.isInfoEnabled()){
            log.info("SubjectInfoDomainServiceImpl.add.bo:{}",subjectInfoBO);
        }

        /*
            假设我们都写在这个主流程中,我们就要判断Type
            单选调用单选的Service,多选的调用多选的,这样就会有很多的if语句的
            这时我们就可以上一个小小的策略,就是工厂+策略的模式
            一个工厂包含4个类型,根据传入的type自动映射选择处理,公共的主表我们就都插入都行了,这里是自己处理自己的单选的处理单选的
         */

        //这个就是插入公共的了主表来的,其他的都是根据各自的type传递的,题目的信息表的
        SubjectInfo subjectInfo = SubjectInfoConverter.INSTANCE.convertBoToInfo(subjectInfoBO);
        subjectInfo.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        infoService.insert(subjectInfo);

        //工厂插入即可
        SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfo.getSubjectType());

        subjectInfoBO.setId(subjectInfo.getId());           //id回显了
        handler.add(subjectInfoBO);

        //插入映射表,和标签的关系表
        List<Integer> categoryIds = subjectInfoBO.getCategoryIds();
        List<Integer> labelIds = subjectInfoBO.getLabelIds();

        //分类和标签是多对多的,2个分类和1个标签的话,插入数据库之后是两个数据的,这样多层级联的关系,这样设计的时候,页面展示非常漂亮的
        List<SubjectMapping> mappingList=new ArrayList<>();

        categoryIds.forEach(categoryId->{
            labelIds.forEach(labelId->{
                SubjectMapping subjectMapping = new SubjectMapping();

                subjectMapping.setSubjectId(subjectInfo.getId());
                subjectMapping.setCategoryId(Long.valueOf(categoryId));
                subjectMapping.setLabelId(Long.valueOf(labelId));
                subjectMapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

                mappingList.add(subjectMapping);
            });
        });


        //批量插入
        mappingService.batchInsert(mappingList);

        //这里我们先使用同步的方式插入到ES中,我们后面改成异步的MQ插入和Canal,组装ES的想要的数据
        SubjectInfoES subjectInfoES=new SubjectInfoES();
        subjectInfoES.setDocId(new IdWorkerUtil(1,1,1).nextId()); //雪花算法自增的id,自己的工具类亲
        subjectInfoES.setSubjectId(subjectInfo.getId());
        subjectInfoES.setSubjectAnswer(subjectInfoBO.getSubjectAnswer());                       //这个答案是通过策略工程获得的,info没有但是我封道bo中了亲
        subjectInfoES.setCreateTime(new Date().getTime());                                      //你也可以在Mapping中设置为date的,我们这里用long
        subjectInfoES.setCreateUser(LoginUtil.getLoginId());                                    //用户亲
        subjectInfoES.setSubjectName(subjectInfo.getSubjectName());
        subjectInfoES.setSubjectType(subjectInfo.getSubjectType());

        //插入ES
        subjectESService.insert(subjectInfoES);


        //插入到redis中,Zadd计入排行榜来的
        redisUtil.addScore(RANK_KEY,LoginUtil.getLoginId(),1);      //这里就是来一个人我们给他+1
    }


    //分页查询
    @Override
    public PageResult<SubjectInfoBO> getSubjectPage(SubjectInfoBO subjectInfoBO) {
        //封装数据结果
        PageResult<SubjectInfoBO> pageResult = new PageResult<>();

        pageResult.setPageNo(subjectInfoBO.getPageNo());
        pageResult.setPageSize(subjectInfoBO.getPageSize());

        //数据的起始索引
        int start = (subjectInfoBO.getPageNo() - 1) * subjectInfoBO.getPageSize();

        SubjectInfo subjectInfo = SubjectInfoConverter.INSTANCE.convertBoToInfo(subjectInfoBO);

        //计算总条数,我们先不去污染我们 的实体类,我们当做参数传递进去
        int count = infoService.countByCondition(subjectInfo, subjectInfoBO.getCategoryId(), subjectInfoBO.getLabelId());

        if (count == 0){
            //直接返回空的
            return pageResult;
        }

        //计算当前页的数据,传递索引进去
        List<SubjectInfo> subjectInfoList= infoService.queryPage(subjectInfo, subjectInfoBO.getCategoryId(), subjectInfoBO.getLabelId(),start,subjectInfoBO.getPageSize());

        //抓换
        List<SubjectInfoBO> subjectInfoBOS=SubjectInfoConverter.INSTANCE.convertListInfoToBo(subjectInfoList);

        //因为info实体是没有page的我们转换后就是默认值了,我们显示的设置一下
        for (SubjectInfoBO infoBO : subjectInfoBOS) {
            infoBO.setPageNo(subjectInfoBO.getPageNo());
            infoBO.setPageSize(subjectInfoBO.getPageSize());
        }

        //封装数据
        pageResult.setRecords(subjectInfoBOS);
        pageResult.setTotal(count);

        return pageResult;
    }


    //查询题目详情
    @Override
    public SubjectInfoBO querySubjectInfo(SubjectInfoBO subjectInfoBO) {
        SubjectInfo subjectInfo = infoService.queryById(subjectInfoBO.getId());
        Integer subjectType = subjectInfo.getSubjectType();                       //题目的类型

        //使用策略模式来
        SubjectTypeHandler handler = handlerFactory.getHandler(subjectType);
        SubjectOptionBO optionBO = handler.query(subjectInfo.getId());              //答案

        SubjectInfoBO bo=SubjectInfoConverter.INSTANCE.convertOptionAndInfoToBo(optionBO,subjectInfo);  //把optionBO变成,private List<SubjectAnswerBO> optionList;

        //查询标签
        SubjectMapping subjectMapping = new SubjectMapping();
        subjectMapping.setSubjectId(subjectInfo.getId());
        subjectMapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        List<SubjectMapping> mappingList = mappingService.queryLabelId(subjectMapping);

        //把labelId收集起来
        List<Long> labelIdList = mappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());
        List<SubjectLabel> labelList = labelService.batchQueryById(labelIdList);

        //把名字收集起来
        List<String> labelNameList = labelList.stream().map(SubjectLabel::getLabelName).collect(Collectors.toList());

        bo.setLabelName(labelNameList);

        //把题目点赞信息传递过去
        Boolean isLiked = likedDomainService.isLiked(subjectInfoBO.getId().toString(), LoginUtil.getLoginId());
        Integer count = likedDomainService.getLikedCount(subjectInfoBO.getId().toString());

        bo.setLikedCount(count);
        bo.setLiked(isLiked);

        //封装题目的游标便于快速刷题来的亲
        assembleSubjectCursor(subjectInfoBO,bo);

        return bo;
    }

    //封装游标,便于下一题和上一题来的,为什么传递两个我们要分离的Id和标签的id,这样我们才能刷同类型的题亲,从数据库查回来的没有分离和标签的数据的
    private void assembleSubjectCursor(SubjectInfoBO subjectInfoBO,SubjectInfoBO bo) {
        Long categoryId = subjectInfoBO.getCategoryId();
        Long labelId = subjectInfoBO.getLabelId();
        Long subjectId = subjectInfoBO.getId();

        //判断一下
        if (Objects.isNull(categoryId) || Objects.isNull(labelId)){
            return;
        }

        //查询下一个和上一题的游标
        Long nextSubjectId=infoService.querySubjectIdCursor(subjectId,categoryId,labelId,1);
        bo.setNextSubjectId(nextSubjectId);

        Long lastSubjectId=infoService.querySubjectIdCursor(subjectId,categoryId,labelId,0);
        bo.setLastSubjectId(lastSubjectId);

        //我们做的有约定,1就是下一题,0就是上一题,这样通用的亲
    }


    //全文检索,返回高亮亲
    @Override
    public PageResult<SubjectInfoES> getSubjectPageBySearch(SubjectInfoBO subjectInfoBO) {
        //转换对象
        SubjectInfoES subjectInfoES = new SubjectInfoES();
        subjectInfoES.setPageNo(subjectInfoBO.getPageNo());
        subjectInfoES.setPageSize(subjectInfoBO.getPageSize());
        subjectInfoES.setKeyWord(subjectInfoBO.getKeyWord());

        return subjectESService.querySubjectList(subjectInfoES);
    }


    //更新题目
    @Override
    @Transactional
    public Boolean update(SubjectInfoBO subjectInfoBO) {
        SubjectInfo subjectInfo = SubjectInfoConverter.INSTANCE.convertBoToInfo(subjectInfoBO);

        Integer count = infoService.update(subjectInfo);

        //工厂插入即可
        SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfo.getSubjectType());
        handler.upDate(subjectInfoBO);

        //插入映射表,和标签的关系表
        List<Integer> categoryIds = subjectInfoBO.getCategoryIds();
        List<Integer> labelIds = subjectInfoBO.getLabelIds();

        //分类和标签是多对多的,2个分类和1个标签的话,插入数据库之后是两个数据的,这样多层级联的关系,这样设计的时候,页面展示非常漂亮的
        List<SubjectMapping> mappingList=new ArrayList<>();

        categoryIds.forEach(categoryId->{
            labelIds.forEach(labelId->{
                SubjectMapping subjectMapping = new SubjectMapping();

                subjectMapping.setSubjectId(subjectInfo.getId());
                subjectMapping.setCategoryId(Long.valueOf(categoryId));
                subjectMapping.setLabelId(Long.valueOf(labelId));
                subjectMapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

                mappingList.add(subjectMapping);
            });
        });


        //批量修改
        mappingService.batchUpdate(mappingList);

        return count>0;
    }

    //获得用户贡献榜信息
    @Override
    public List<SubjectInfoBO> getContributeList1() {
        //对于我们的Count不是我们的数据库字段来的亲
        List<SubjectInfo> subjectInfoList=infoService.getContributeCount();

        //返回空的
        if (CollectionUtils.isEmpty(subjectInfoList)){
            return Collections.emptyList();
        }

        List<SubjectInfoBO> boList=new ArrayList<>();

        //封装数据
        subjectInfoList.forEach(subjectInfo ->{
            SubjectInfoBO subjectInfoBO = new SubjectInfoBO();
            subjectInfoBO.setSubjectCount(subjectInfo.getSubjectCount());

            //远程调用用户的服务,其实这个是用户的昵称这个还要去调用RPC了
            UserInfo userInfo = userRPC.getUserInfo(subjectInfo.getCreatedBy());
            subjectInfoBO.setCreateUser(userInfo.getNickName());        //昵称
            subjectInfoBO.setCreateUserAvatar(userInfo.getAvator());    //头像

            boList.add(subjectInfoBO);
        });

        return boList;
    }

    //redis实现排行榜
    @Override
    public List<SubjectInfoBO> getContributeList2() {
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisUtil.rankWithScore(RANK_KEY, 0, 5);       //我们要前5名来的

        if (log.isInfoEnabled()){
            log.info("getContributeList2.typedTuples:{}", JSON.toJSONString(typedTuples));
        }

        //空的直接返回
        if (CollectionUtils.isEmpty(typedTuples)){
            return Collections.emptyList();
        }

        //封装数据返回
        List<SubjectInfoBO> boList=new ArrayList<>();
        typedTuples.forEach(rank->{
            SubjectInfoBO subjectInfoBO = new SubjectInfoBO();
            subjectInfoBO.setSubjectCount(rank.getScore().intValue());

            //远程调用用户的服务,其实这个是用户的昵称这个还要去调用RPC了
            UserInfo userInfo = userRPC.getUserInfo(rank.getValue());   //这个就是用户名来的亲
            subjectInfoBO.setCreateUser(userInfo.getNickName());        //昵称
            subjectInfoBO.setCreateUserAvatar(userInfo.getAvator());    //头像

            boList.add(subjectInfoBO);

        });

        return boList;
    }

}
